<template>
  <el-form
    ref="formRef"
    class="z-form"
    :class="{ 'is-fixed-width': Boolean(itemWidth) }"
    :model="editForm"
    :rules="ruleForm"
    @submit.native.prevent="onSubmit"
  >
    <template v-for="(item, key) in forms">
      <el-form-item
        v-show="!item.hidden"
        :prop="key.toString()"
        :label-width="item.labelWidth"
        :class="item.type"
        :style="{ '--z-width': item.itemWidth || itemWidth }"
        v-bind:style="item.itemStyle"
      >
        <!-- label 插槽 -->
        <template #label v-if="item.label">
          {{ $t(item.label) }}
          <el-tooltip v-if="item.tip" placement="top">
            <template #content>
              <span v-if="typeof item.tip === 'string'">{{ $t(item.tip) }}</span>
              <span v-else>{{ item.tip() }}</span>
            </template>
            <el-text type="warning" class="z-form__tip-icon">
              <el-icon>
                <warning />
              </el-icon>
            </el-text>
          </el-tooltip>
        </template>
        <div class="z-form-input">
          <t-form-item
            v-model="editForm[key]"
            :i18n="i18n"
            :i18nPrefix="i18nPrefix"
            v-bind="item"
          ></t-form-item>
          <template v-if="item.itemAppend">
            <template v-if="typeof item.itemAppend === 'object'">
              <Component :is="item.itemAppend" />
            </template>
            <span v-else>{{ item.itemAppend }}</span>
          </template>
        </div>
      </el-form-item>
    </template>

    <slot />
  </el-form>
</template>

<script lang="ts" setup>
import { Warning } from '@element-plus/icons-vue'
import deepClone from '@/utils/deep-clone'
import tFormItem from './form-item/index.vue'
import { useI18n } from 'vue-i18n'
import type { Props } from './type'

const props = withDefaults(defineProps<Props>(), {})

const { t } = useI18n()
const $t = (text: string = '') => {
  return props.i18n && text ? t((props.i18nPrefix ? props.i18nPrefix + '.' : '') + text) : text
}

// 事件
const emits = defineEmits<{
  submit: [model: AnyObject]
}>()

const formRef = useTemplateRef('formRef')

// 编辑项
let editForm = reactive<AnyObject>({})
// 校验项
let ruleForm = reactive<AnyObject>({})

// 校验表单
const validate = (): Promise<AnyObject> => {
  const _forms = formRef.value
  return new Promise((resolve, reject) => {
    _forms?.validate(valid => {
      const model = deepClone(editForm)
      if (valid) {
        resolve(model)
      } else {
        reject(model)
      }
    })
  })
}

// 部分字段
const validateField = (props: string, callback?: (...arg: any) => any) => {
  const _forms = formRef.value
  _forms?.validateField(props, callback)
}

// 重置
const reset = (callback?: (model: AnyObject) => void) => {
  const _forms = formRef.value
  _forms?.resetFields()

  typeof callback == 'function' && callback(deepClone(editForm))
}

// 提交
const onSubmit = () => {
  validate().then(model => {
    emits('submit', model)
  })
}

// 复制
const copy = (obj: AnyObject, copyObj: AnyObject) => {
  Object.keys(copyObj).forEach(key => {
    if (obj[key] !== copyObj[key]) {
      if (typeof obj[key] === 'object') {
        if (isEqual(obj[key], copyObj[key])) return
      }
      obj[key] = copyObj[key]
    }
  })
}

// 转换表单数据
const convertFormData = () => {
  const eForm: AnyObject = {}
  const rorm: AnyObject = {}
  const forms = deepClone(props.forms || {})
  Object.keys(forms).forEach(key => {
    // 值
    let _val_ = forms[key].value
    // 类型
    let _ty_ = forms[key].type || ''

    // 默认值
    let _v_ =
      _val_ != void 0
        ? _val_
        : ['checkbox'].includes(_ty_)
        ? []
        : ['number', 'rate', 'slider'].includes(_ty_)
        ? 0
        : ['switch'].includes(_ty_)
        ? true
        : ''

    eForm[key] = _v_

    // 校验
    if (forms[key].rules && !forms[key].hidden) {
      rorm[key] = forms[key].rules
    }
  })
  copy(editForm, eForm)
  ruleForm.value = {}
  copy(ruleForm, rorm)
}
convertFormData()

// 表单 key 值
const formKeys = computed(() => Object.keys(props.forms))

//  表单 value
const formValues = computed(() => {
  const formData = props.forms
  return formKeys.value.map(key => {
    return formData[key].value
  })
})

// 表单 rules + hidden
const formRulesAndHide = computed(() => {
  const formData = props.forms
  return formKeys.value.map(key => {
    return {
      // 使用 JSON.stringify 字符串话 监听对比后会拿到新旧不相等的值
      rules: JSON.stringify(formData[key].rules),
      hidden: formData[key].hidden
    }
  })
})

// 监听值变化
watch(
  formValues,
  (nv, ov) => {
    // 查找不等
    for (let i = 0; i < nv.length; i++) {
      const nvl = nv[i]
      const ovl = ov[i]
      // 判断不全等
      if (!isEqual(nvl, ovl)) {
        const key = formKeys.value[i]
        setModel(key, nvl)
      }
    }
  },
  {
    deep: true
  }
)

// 深度比较
const isEqual = (a: any, b: any) => {
  // 处理基本类型和相同引用
  if (a === b) return true

  // 处理NaN
  if (Number.isNaN(a) && Number.isNaN(b)) return true

  // 处理null和undefined
  if (a == null || b == null) return a === b

  // 检查类型是否一致
  if (typeof a !== typeof b) return false

  // 处理日期对象
  if (a instanceof Date && b instanceof Date) {
    return a.getTime() === b.getTime()
  }

  // 处理正则表达式
  if (a instanceof RegExp && b instanceof RegExp) {
    return a.toString() === b.toString()
  }

  // 处理函数
  if (typeof a === 'function' && typeof b === 'function') {
    return a.toString() === b.toString()
  }

  // 处理数组和对象
  if (typeof a === 'object' && typeof b === 'object') {
    const keysA = Object.keys(a)
    const keysB = Object.keys(b)

    if (keysA.length !== keysB.length) return false

    for (let key of keysA) {
      if (!keysB.includes(key)) return false
      if (!isEqual(a[key], b[key])) return false
    }
    return true
  }

  // 其他情况
  return false
}

// 监听校验和隐藏字段
watch(
  formRulesAndHide,
  (nv, ov) => {
    // 查找不等
    for (let i = 0; i < nv.length; i++) {
      const nvl = nv[i]
      const ovl = ov[i]
      // 判断不全等
      if (!isEqual(nvl, ovl)) {
        const key = formKeys.value[i]

        if (!nvl.hidden) {
          ruleForm[key] = props.forms[key].rules
        } else {
          ruleForm[key] = void 0
        }
      }
    }
  },
  {
    deep: true
  }
)

// 设置值 - 避免直接设置表单 value 值 防止触发监听
const setModel = (key: string, val: any) => {
  editForm[key] = val
}

// 暴露
defineExpose({
  validate,
  validateField,
  reset,
  get model() {
    return toRaw(editForm)
  },
  get rules() {
    return toRaw(ruleForm)
  },
  setModel
})
</script>

<style lang="scss">
@use './style.scss';
</style>
